孙鑫VC++第15课:多线程与聊天室02 |
您所在的位置:网站首页 › vc6 多线程 › 孙鑫VC++第15课:多线程与聊天室02 |
3、使用多线程实现聊天室功能 新建MFC工程,通过向导创建一个基于对话框的应用程序。 3.1 界面设计 删除对话框原来默认的组件,添加组框(caption="接收数据"),在组框中放置编辑框(ID=IDC_EDIT_RECV);下面再增加一个组框(caption="发送数据")其中添加一个ip地址控件,右侧增加一个编辑框(ID=IDC_EDIT_SEND),添加一个按钮(caption="发送",ID=ID_BTN_SEND)。 界面如下所示:
3.2 加载套接字库 网络编程第一步,加载套接字库,进行套接字库版本协商。在MFC中,提供了一个加载套接字库和完成版本协商的函数AfxSocketInit。 BOOL AfxSocketInit(WSADATA * lpwsaData = NULL);//lpwsaData:指向WSADATA结构体的指针 函数内部加载套接字库、进行版本协商(加载的是1.1版本的套接字库)。函数可确保应用程序终止之前,调用wsacleanup终止对套接字的使用。 Remarks:在CWinApp::InitInstance中调用该函数。 调用AfxSocketInit函数时,需要包含一个头文件Afxsock.h BOOL CChatApp::InitInstance() { if(!AfxSocketInit())//套接字库加载失败则返回 { AfxMessageBox(_T("加载套接字库失败!")); return FALSE; }
3.3 创建套接字 在CChatDlg类中,增加一个成员函数,完成套接字的初始化。BOOL CChatDlg::InitSocket() CChatDlg类中增加成员变量private SOCKET m_socket(套接字描述符) 在套接字初始化函数中创建套接字。 1 BOOL CChatDlg::InitSocket(void) 2 { 3 m_socket = socket(AF_INET, SOCK_DGRAM, 0);//创建一个基于UDP的套接字 4 if (INVALID_SOCKET == m_socket) 5 { 6 AfxMessageBox(_T("套接字创建失败!")); 7 return FALSE; 8 } 9 /*本程序既包含接收端,又包含发送端。接收端程序需要绑定IP地址和端口号*/ 10 SOCKADDR_IN addSock; 11 addSock.sin_family = AF_INET; 12 addSock.sin_port = htons(6000); 13 addSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY); 14 15 //绑定 16 int ret; 17 ret = bind(m_socket, (SOCKADDR*)&addSock, sizeof(SOCKADDR)); 18 if (SOCKET_ERROR == ret)//绑定失败则返回 19 { 20 closesocket(m_socket);//关闭套接字 21 AfxMessageBox(_T("绑定失败")); 22 return FALSE; 23 } 24 return TRUE; 25 }在OnInitDialog函数中调用Socket的初始化函数 1 BOOL CChatDlg::OnInitDialog() 2 { 3 CDialog::OnInitDialog(); 4 5 // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 6 // 执行此操作 7 SetIcon(m_hIcon, TRUE); // 设置大图标 8 SetIcon(m_hIcon, FALSE); // 设置小图标 9 10 // TODO: 在此添加额外的初始化代码 11 InitSocket();//初始化套接字 12 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE 13 }
3.4 接收端程序设计 当接收数据时,在没有数据到来时,函数recvfrom会阻塞,导致程序暂停运行。 因此应该把接收数据的工作放到一个单独的线程中完成。 3.4.1 接收端线程创建 使用CreateThread函数创建线程,并给线程传递两个参数:一是创建的套接字;二是对话框的句柄或者“数据接收编辑框”的句柄。如此在线程中接收到数据后,可以将数据传回给对话框或编辑框,用于显示在,界面中。 由于CreateThread函数只接受一个指针变量作为参数,因此需要创建一个结构体,将待传递的两个参数包到结构体中进行传递。在CChatDlg的头文件中定义结构体。 1 //接收数据参数的结构体 2 struct RECVPARAM 3 { 4 SOCKET sock; 5 HWND hWnd; 6 };再在CChatDialog::OnInitDlg中定义该结构体指针,并初始化数据。 BOOL CChatDlg::OnInitDialog() { CDialog::OnInitDialog(); // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码 InitSocket();//初始化套接字 RECVPARAM* pRecvParam = new RECVPARAM; pRecvParam->sock = m_socket; pRecvParam->hWnd = m_hWnd;//对话框类与窗口相关的句柄使用CreateThread创建线程 1 BOOL CChatDlg::OnInitDialog() 2 { 3 CDialog::OnInitDialog(); 4 5 // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 6 // 执行此操作 7 SetIcon(m_hIcon, TRUE); // 设置大图标 8 SetIcon(m_hIcon, FALSE); // 设置小图标 9 10 // TODO: 在此添加额外的初始化代码 11 InitSocket();//初始化套接字 12 RECVPARAM* pRecvParam = new RECVPARAM; 13 pRecvParam->sock = m_socket; 14 pRecvParam->hWnd = m_hWnd;//对话框类与窗口相关的句柄 15 16 HANDLE hThreadRecv = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParam, 0, NULL);//创建接收线程 17 CloseHandle(hThreadRecv);//关闭线程句柄 18 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE 19 }3.4.2 线程函数设计 不能将线程函数定义为CChatDlg类的普通成员函数,因为当创建一个线程时,系统(运行时代码)需要调用线程函数启动线程。如果线程函数是CChatDlg类的成员函数,在调用成员函数前,需要先构造CChatDlg类的对象,通过对象调用成员函数。运行时代码无法确定如何生成对象。 解决方法是将线程函数定义成类的静态函数(或者全局函数)。 public: static DWORD WINAPI RecvProc(LPVOID lpParameter); 1 DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter) 2 { 3 //取出所传递的参数值 4 SOCKET sock = ((RECVPARAM*)lpParameter)->sock; 5 HWND hWnd = ((RECVPARAM*)lpParameter)->hWnd; 6 7 SOCKADDR_IN addrFrom;//用于接收发送端的地址信息 8 int len = sizeof(SOCKADDR); 9 10 char recvBuf[200];//用于保存接收到的数据 11 char tempBuf[300];//存放格式化后的数据 12 13 int retval; 14 while(TRUE) 15 { 16 retval = recvfrom(sock, recvBuf, 200, 0, (SOCKADDR*)&addrFrom, &len);//接收数据 17 if (SOCKET_ERROR == retval)//接收错误则返回 18 { 19 break; 20 } 21 sprintf(tempBuf, "%s说:%s", inet_ntoa(addrFrom.sin_addr), recvBuf);//格式化数据 22 //将接收到的数据传递给对话框(使用发送消息的方式,并在CChatDlg头文件中定义消息的值) 23 ::PostMessage(hWnd, WM_RECVDATA, 0, (LPARAM)tempBuf); 24 } 25 26 return 0; 27 }3.4.3 自定义消息及响应 3.4.3.1 自定义消息 在CChatDlg头文件中定义消息 #define WM_RECVDATA WM_USER+13.4.3.2 定义消息响应函数 在CChatDlg头文件中定义消息响应函数 afx_msg void OnRecvData(WPARAM wParam, LPARAM lParam);//声明消息响应函数 DECLARE_MESSAGE_MAP()3.4.3.3 定义消息映射(消息与消息响应函数的映射) BEGIN_MESSAGE_MAP(CChatDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_MESSAGE(WM_RECVDATA, OnRecvData);//完成消息映射 //}}AFX_MSG_MAP END_MESSAGE_MAP()3.4.3.4 消息响应函数的设计 1 void CChatDlg::OnRecvData(WPARAM wParam, LPARAM lParam) 2 { 3 CString str = (char*)lParam;//保存新接收的数据 4 CString strOld;//保存聊天记录 5 GetDlgItemText(IDC_EDIT_RECV, strOld);//获取旧的聊天记录 6 str+="\r\n";//增加换行 7 str+=strOld;//拼接旧的聊天记录 8 SetDlgItemText(IDC_EDIT_RECV, str);//显示接收到的数据 9 }
3.5 发送端程序设计 在"发送"按钮的Click控件点击响应中,完成发送功能设计。 从IP地址控件中,获取对应的ip地址。与ip地址控件对应的类CIPAddressCtrl,通过其成员函数GetAddress获取控件中的IP地址。 int CIPAddressCtrl::GetAddress(DWORD &dwAddress); 1 void CChatDlg::OnBnClickedButtonSend() 2 { 3 // TODO: Add your control notification handler code here 4 DWORD dwIP;//接收用户输入的IP地址 5 ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);//获取用户输入的IP 6 7 SOCKADDR_IN addrTo; 8 addrTo.sin_family = AF_INET; 9 addrTo.sin_port = htons(6000); 10 addrTo.sin_addr.S_un.S_addr = htonl(dwIP); 11 12 CString strSend; 13 //char strSend[200]; 14 //memset(strSend, 0, 200); 15 //USES_CONVERSION; 16 GetDlgItemText(IDC_EDIT_SEND, strSend); 17 sendto(m_socket, (LPCSTR)(LPCTSTR)strSend, strSend.GetLength()+1, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR)); 18 SetDlgItemText(IDC_EDIT_SEND, _T(""));//设置发送框内容为空 19 }
|
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |